package step

import (
	"crypto/ecdsa"
	"errors"
	"fmt"
	"github.com/wanchain/go-mpc/crypto"
	"math/big"
	"strconv"

	"github.com/wanchain/go-mpc/common/hexutil"
	"github.com/wanchain/go-mpc/log"
	"github.com/wanchain/go-mpc/p2p/discover"
	"github.com/wanchain/go-mpc/storeman/osmconf"
	mpcprotocol "github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
)

// find valid group
type PeerGroupStep struct {
	BaseStep
	rcvColInterMap map[*discover.NodeID]*big.Int
	rcvColInter    *big.Int
	rcvCol         *big.Int
	sigNum         int
}

func CreateMpcPeerGroupStep(totoalNum int, sigNum int) *PeerGroupStep {
	log.SyslogInfo("CreateMpcPeerGroupStep begin")

	mpc := &PeerGroupStep{
		*CreateBaseStep(totoalNum, -1),
		make(map[*discover.NodeID]*big.Int, 0),
		nil, nil, sigNum}

	return mpc
}

func (step *PeerGroupStep) InitStep(result mpcprotocol.MpcResultInterface) error {
	log.SyslogDebug("PeerGroupStep.InitStep begin", "ctx", step.GetMpcContextId())
	step.BaseStep.InitStep(result)

	ret, err := result.GetValue(mpcprotocol.MpcPeerRcvIntersaction)
	if err != nil {
		log.SyslogErr("PeerGroupStep.InitStep", "GetValue error", err.Error(), "ctx", step.GetMpcContextId())
		return err
	}
	if len(ret) == 0 {
		log.SyslogErr("PeerGroupStep.InitStep", "GetValue len(ret)", len(ret), "ctx", step.GetMpcContextId())
		return err
	}
	step.rcvColInter = &ret[0]

	ret, err = result.GetValue(mpcprotocol.BTCRecvCol)
	if err != nil {
		log.SyslogErr("PeerGroupStep.InitStep", "GetValue error", err.Error(), "ctx", step.GetMpcContextId())
		return err
	}
	if len(ret) == 0 {
		log.SyslogErr("PeerGroupStep.InitStep", "GetValue len(ret)", len(ret), "ctx", step.GetMpcContextId())
		return err
	}
	step.rcvCol = &ret[0]

	log.SyslogDebug("PeerGroupStep.InitStep end", "MpcPeerRcvIntersaction", hexutil.Encode(step.rcvColInter.Bytes()), "BTCRecvCol", hexutil.Encode(step.rcvCol.Bytes()), "ctx", step.GetMpcContextId())
	return nil
}

func (step *PeerGroupStep) CreateMessage() []mpcprotocol.StepMessage {
	log.SyslogDebug("PeerGroupStep.CreateMessage begin", "ctx", step.GetMpcContextId())

	message := make([]mpcprotocol.StepMessage, 1)

	message[0].MsgCode = mpcprotocol.MPCMessage
	message[0].PeerID = nil
	message[0].Data = make([]big.Int, 1)
	message[0].Data[0] = *step.rcvColInter
	log.SyslogDebug("PeerGroupStep.CreateMessage", "PeerID", "nil")

	log.SyslogDebug("PeerGroupStep.CreateMessage end", "ctx", step.GetMpcContextId())

	return message
}

func (step *PeerGroupStep) HandleMessage(msg *mpcprotocol.StepMessage) bool {
	log.SyslogInfo("PeerGroupStep.HandleMessage begin", "ctx", step.GetMpcContextId())

	if len(msg.Data) > 0 {
		step.rcvColInterMap[msg.PeerID] = &msg.Data[0]
		log.SyslogInfo("PeerGroupStep.HandleMessage", "my_Intersection", hexutil.Encode(step.rcvColInter.Bytes()),
			"peerID", msg.PeerID.String(), "peer's_intersection", hexutil.Encode(msg.Data[0].Bytes()), "ctx", step.GetMpcContextId())
	}

	log.SyslogInfo("PeerGroupStep.HandleMessage end", "ctx", step.GetMpcContextId())

	return true
}

func (step *PeerGroupStep) FinishStep(result mpcprotocol.MpcResultInterface, mpc mpcprotocol.StoremanManager) error {
	log.SyslogDebug("PeerGroupStep.FinishStep begin", "ctx", step.GetMpcContextId())
	var recieveIndex []uint16

	err := step.BaseStep.FinishStep()
	if err != nil {
		return err
	}

	_, grpIdString, err := osmconf.GetGrpId(result)
	if err != nil {
		log.SyslogErr("PeerGroupStep.FinishStep", "error in GetGrpId", err.Error(), "ctx", step.GetMpcContextId())
		return err
	}

	leaderIndex, _ := osmconf.GetOsmConf().GetLeaderIndex(grpIdString)
	bIncludeLeader, err := osmconf.IsHaming(step.rcvColInter, leaderIndex)
	if err != nil {
		log.SyslogErr("PeerGroupStep.FinishStep", "error in IsHaming", err.Error(), "ctx", step.GetMpcContextId())
		return err
	}

	// 1. should include leaderIndex
	if !bIncludeLeader {
		log.SyslogErr("PeerGroupStep.FinishStep leader is not included in the intersection", "ctx", step.GetMpcContextId())
		return errors.New(fmt.Sprintf("PeerGroupStep.FinishStep leader is not included in the intersection"))
	} else {
		log.SyslogDebug("PeerGroupStep.FinishStep rcvColInter include leader index", "ctx", step.GetMpcContextId())
	}

	// 2. others' inter collection should be equal to self's inter collection
	rcvColInterAcc := big.NewInt(0)
	rcvColInterCount := 0

	for nodeId, rcvColInter := range step.rcvColInterMap {
		tempIndex, err := osmconf.GetOsmConf().GetInxByNodeId(grpIdString, nodeId)
		if err != nil {
			log.SyslogErr("PeerGroupStep.FinishStep", "GetInxByNodeId eror", err.Error(), "grpIdString", grpIdString, "nodeId", nodeId.String())
		}
		recieveIndex = append(recieveIndex, tempIndex)

		if step.rcvColInter.Cmp(rcvColInter) == 0 {
			idx, _ := osmconf.GetOsmConf().GetInxByNodeId(grpIdString, nodeId)
			rcvColInterAcc.SetBit(rcvColInterAcc, int(idx), 1)
			rcvColInterCount++
		} else {
			log.SyslogInfo("PeerGroupStep.FinishStep", "drop nodeId", nodeId)
			step.MpcContext.InsertDropPeers([]discover.NodeID{*nodeId})
		}
	}

	// drop the peer not in the collections
	nodesNeedDrop, waitCount, err := step.BaseStep.GetNodeIdsNotInColletion(grpIdString, step.rcvColInter)
	if err != nil {
		return err
	}
	log.SyslogInfo("PeerGroupStep.FinishStep",
		"waitCount", waitCount,
		"InsertDropPeers drop nodeId", discover.ArrNodeIdsToStr(nodesNeedDrop),
		"self rcv collection intersection", hexutil.Encode(step.rcvColInter.Bytes()),
		"ctx", step.GetMpcContextId())
	step.MpcContext.InsertDropPeers(nodesNeedDrop)
	if waitCount > 0 {
		step.MpcContext.UptAllStepsWtCount(waitCount)
	}

	log.SyslogInfo("PeerGroupStep.FinishStep", "rcvColInterCount", rcvColInterCount, "rcvColInterAcc", hexutil.Encode((*rcvColInterAcc).Bytes()), "step.rcvColInter", hexutil.Encode((*(step.rcvColInter)).Bytes()))

	allIndex, _ := osmconf.GetOsmConf().GetGrpElemsInxes(grpIdString)
	losingIndex := osmconf.Difference(*allIndex, recieveIndex)

	log.SyslogInfo("PeerGroupStep.FinishStep",
		"losing nodes", osmconf.GetOsmConf().BuildLosingByIndex(grpIdString, losingIndex), "ctx", step.GetMpcContextId())

	threshold, _ := osmconf.GetOsmConf().GetThresholdNum(grpIdString)
	if rcvColInterCount < int(threshold) {
		log.SyslogErr("PeerGroupStep.FinishStep rcvColInterAcc < threshold", "ctx", step.GetMpcContextId())
		return errors.New(fmt.Sprintf("%s %s", mpcprotocol.ErrTooLessDataCollected.Error(), osmconf.GetOsmConf().BuildLosingByIndex(grpIdString, losingIndex)))

	}

	// todo my_intesection == rcvCol, no need to update JRJZ data and PointA data
	err = step.UpdateData(step.rcvCol, step.rcvColInter)
	if err != nil {
		return err
	}
	log.SyslogDebug("PeerGroupStep.FinishStep end", "ctx", step.GetMpcContextId())
	return nil
}

func (step *PeerGroupStep) UpdateData(rcvCol, rcvInterCol *big.Int) error {
	//if rcvCol.Cmp(rcvInterCol) == 0 {
	//	log.SyslogInfo("PeerGroupStep UpdateData. no need to update data", "ctx", step.GetMpcContextId())
	//	return nil
	//}

	log.SyslogInfo("PeerGroupStep UpdateData. updating data", "ctx", step.GetMpcContextId(),
		"rcvCol", hexutil.Encode(rcvCol.Bytes()),
		"rcvInterCol", hexutil.Encode(rcvInterCol.Bytes()))

	result := step.mpcResult
	_, grpIdStr, _ := osmconf.GetGrpId(result)
	totoalNumber, _ := osmconf.GetOsmConf().GetTotalNum(grpIdStr)

	// update JRJZ
	for i := 0; i < step.sigNum; i++ {
		iStr := "_" + strconv.Itoa(i)

		aResult := big.NewInt(0)
		rResult := big.NewInt(0)
		bResult := big.NewInt(0)
		cResult := big.NewInt(0)

		for j := 0; j < int(totoalNumber); j++ {
			ok, _ := osmconf.IsHaming(rcvInterCol, uint16(j))
			if ok {

				key := mpcprotocol.JRJZData + "_" + strconv.Itoa(i) + "_" + strconv.Itoa(int(j))
				ret, err := result.GetValue(key)
				if err != nil {
					log.SyslogErr("PeerGroupStep::UpdateData", "Getvalue error", "key", key, "ctx", step.GetMpcContextId())
				}
				aResult.Add(aResult, &ret[0])
				aResult.Mod(aResult, crypto.Secp256k1_N)

				rResult.Add(rResult, &ret[1])
				rResult.Mod(rResult, crypto.Secp256k1_N)

				bResult.Add(bResult, &ret[2])
				bResult.Mod(bResult, crypto.Secp256k1_N)

				cResult.Add(cResult, &ret[3])
				cResult.Mod(cResult, crypto.Secp256k1_N)
			}
		}

		err := result.SetValue(mpcprotocol.MpcSignA+iStr, []big.Int{*aResult})
		if err != nil {
			log.SyslogErr("PeerGroupStep::UpdateData", "SetValue error", "key", mpcprotocol.MpcSignA+iStr, "ctx", step.GetMpcContextId())
			return err
		}
		err = result.SetValue(mpcprotocol.MpcSignR+iStr, []big.Int{*rResult})
		if err != nil {
			log.SyslogErr("PeerGroupStep::UpdateData", "SetValue error", "key", mpcprotocol.MpcSignR+iStr, "ctx", step.GetMpcContextId())
			return err
		}
		err = result.SetValue(mpcprotocol.MpcSignB+iStr, []big.Int{*bResult})
		if err != nil {
			log.SyslogErr("PeerGroupStep::UpdateData", "SetValue error", "key", mpcprotocol.MpcSignB+iStr, "ctx", step.GetMpcContextId())
			return err
		}

		err = result.SetValue(mpcprotocol.MpcSignC+iStr, []big.Int{*cResult})
		if err != nil {
			log.SyslogErr("PeerGroupStep::UpdateData", "SetValue error", "key", mpcprotocol.MpcSignC+iStr, "ctx", step.GetMpcContextId())
			return err
		}

		ar := make([]big.Int, 1)
		ar[0].Mul(aResult, rResult)
		ar[0].Mod(&ar[0], crypto.Secp256k1_N)
		ar[0].Add(&ar[0], bResult)
		ar[0].Mod(&ar[0], crypto.Secp256k1_N)
		err = result.SetValue(mpcprotocol.MpcSignARSeed+iStr, ar)
	}
	// update Point

	for i := 0; i < step.sigNum; i++ {
		ptResult := new(ecdsa.PublicKey)

		var k = 0
		iStr := "_" + strconv.Itoa(i)
		for j := 0; j < int(totoalNumber); j++ {
			ok, _ := osmconf.IsHaming(rcvInterCol, uint16(j))
			if ok {
				key := mpcprotocol.BTCMpcPointData + iStr + "_" + strconv.Itoa(int(j))
				value, err := result.GetValue(key)
				if err != nil {
					return err
				}
				if k == 0 {
					ptResult.X = new(big.Int).Set(&value[0])
					ptResult.Y = new(big.Int).Set(&value[1])
					k++
				} else {
					ptResult.X, ptResult.Y = crypto.S256().Add(ptResult.X, ptResult.Y, &value[0], &value[1])
				}
			}
		}

		err := result.SetValue(mpcprotocol.MpcSignAPoint+iStr, []big.Int{*ptResult.X, *ptResult.Y})
		if err != nil {
			log.SyslogErr("PeerGroupStep::UpdateData", "SetValue error", "key", mpcprotocol.MpcSignAPoint+iStr, "ctx", step.GetMpcContextId())
			return err
		}
	}

	return nil
}
